package com.liruo.enumconverter.autoconfigure;

import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.gson.Gson;
import com.liruo.enumconverter.annotation.EnableSourceEnumConverter;
import com.liruo.enumconverter.annotation.SourceEnums;
import com.liruo.enumconverter.autoconfigure.properties.EnumConverterProperties;
import com.liruo.enumconverter.autoconfigure.properties.EnumValueStrategy;
import com.liruo.enumconverter.core.converter.IntStrToEnumConverterFactory;
import com.liruo.enumconverter.core.converter.SourceEnumGenericConverter;
import com.liruo.enumconverter.core.gson.IntEnumTypeAdapterFactory;
import com.liruo.enumconverter.core.gson.SourceEnumGsonDeserializer;
import com.liruo.enumconverter.core.gson.SourceEnumGsonSerializer;
import com.liruo.enumconverter.core.jackson.SourceEnumJackDeserializer;
import com.liruo.enumconverter.core.jackson.SourceEnumJackSerializer;
import com.liruo.enumconverter.core.jackson.SourceEnumModule;
import com.liruo.enumconverter.core.mybatis.SourceEnumTypeHandler;
import com.liruo.enumconverter.model.SourceEnum;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.EnumOrdinalTypeHandler;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.gson.GsonBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author:liruo
 * @Date:2023-02-22-16:59:28
 * @Desc
 */
@EnableConfigurationProperties(EnumConverterProperties.class)
@Slf4j
@AutoConfigureBefore(name = {
        "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration",
        "org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration",
        "org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration",
        "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration",
        "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration"
})
@AutoConfiguration
public class EnumConverterAutoConfiguration {
    private SourceEnums sourceEnums;
    private EnumConverterProperties enumConverterProperties;

    public EnumConverterAutoConfiguration(@Autowired(required = false) SourceEnums sourceEnums, EnumConverterProperties enumConverterProperties) {
        check(sourceEnums, enumConverterProperties);
        this.sourceEnums = sourceEnums;
        this.enumConverterProperties = enumConverterProperties;
    }

    private void check(SourceEnums sourceEnums, EnumConverterProperties enumConverterProperties) {
        if(log.isInfoEnabled()){
            log.info("EnumValueStrategy: webmvc={}, jackson={}, gson={}, mybatis={}",
                    enumConverterProperties.getWebMvc(),
                    enumConverterProperties.getJackson(),
                    enumConverterProperties.getGson(),
                    enumConverterProperties.getMybatis()
            );
        }
        //如果是SourceMode, 则sourceEnums不能为null
        if(hasSourceMode(enumConverterProperties)){
            if(sourceEnums == null){
                throw new IllegalArgumentException(
                        "can't use the EnumConverter for "
                                + "your enum-converter config contain source strategy but has not use the annotation= "
                                + EnableSourceEnumConverter.class.getName()
                                + ", this annotation is be used to auto-get the package of sourceEnum"
                );
            }
            if(CollectionUtils.isEmpty(sourceEnums.getSourceEnumSet())){
                if(log.isInfoEnabled()){
                    log.info("can't find the sourceEnums, aren't you sure you only use name or ordinal mode?");
                }
            }
        }
    }
    private boolean hasSourceMode(EnumConverterProperties enumConverterProperties) {
        return enumConverterProperties.getWebMvc() == EnumValueStrategy.SOURCE
                ||enumConverterProperties.getJackson() == EnumValueStrategy.SOURCE
                || enumConverterProperties.getGson() == EnumValueStrategy.SOURCE
                || enumConverterProperties.getMybatis() == EnumValueStrategy.SOURCE;

    }

    @Bean
    public SourceEnumGenericConverter sourceEnumGenericConverter(){
        return new SourceEnumGenericConverter();
    }

    @NoArgsConstructor
    @ConditionalOnWebApplication
    @Configuration
    private class WebMvcConfiguration implements WebMvcConfigurer {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            EnumValueStrategy webMvc = enumConverterProperties.getWebMvc();
            if(webMvc == EnumValueStrategy.SOURCE){
                registry.addConverter(sourceEnumGenericConverter());
            }else if(webMvc == EnumValueStrategy.ORDINAL){
                registry.addConverterFactory(new IntStrToEnumConverterFactory());
            }
        }
    }

    @NoArgsConstructor
    @ConditionalOnClass({ObjectMapper.class})
    @Configuration
    private class JacksonConfiguration{
        @Bean
        public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
            EnumValueStrategy jackson = enumConverterProperties.getJackson();
            if(jackson == EnumValueStrategy.SOURCE){
                SourceEnumModule sourceEnumModule = new SourceEnumModule();
                sourceEnumModule.addSourceEnumImpls(
                        EnumConverterAutoConfiguration.this.sourceEnums.getSourceEnumSet(),
                        new SourceEnumJackDeserializer(sourceEnumGenericConverter()),
                        new SourceEnumJackSerializer()
                );
                return builder->builder.modules(sourceEnumModule);
            }else if(jackson == EnumValueStrategy.ORDINAL){
                return builder->builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_INDEX);
            }
            return builder -> {};
        }
    }

    @NoArgsConstructor
    @ConditionalOnClass({Gson.class})
    @Configuration
    private class GsonConfiguration{
        @Bean
        public GsonBuilderCustomizer gsonBuilderCustomizer(){
            EnumValueStrategy gson = enumConverterProperties.getGson();
            if(gson == EnumValueStrategy.SOURCE){
                return builder->
                        builder.registerTypeHierarchyAdapter(SourceEnum.class,
                                        new SourceEnumGsonDeserializer(sourceEnumGenericConverter()))
                                .registerTypeHierarchyAdapter(SourceEnum.class,
                                        new SourceEnumGsonSerializer());
            }else if(gson == EnumValueStrategy.ORDINAL){
                return builder->
                        builder.registerTypeAdapterFactory(new IntEnumTypeAdapterFactory());
            }else{
                return builder->{};
            }
        }
    }
//    1. mybatis-plus.configuration.default-enum-type-handler=xxx
//    2. CompositeEnumTypeHandler.setDefaultEnumTypeHandler(SourceEnumTypeHandler .class);
//    3. mybatisPlusProperties.getConfiguration().setDefaultEnumTypeHandler(SourceEnumTypeHandler.class);
    private <T extends  org.apache.ibatis.session.Configuration> void mybatis(T configuration){
        EnumValueStrategy mybatis = enumConverterProperties.getMybatis();
        if(mybatis == EnumValueStrategy.SOURCE){
            sourceEnums.getSourceEnumSet()
                    .forEach(sourceEnumClass ->
                            configuration.getTypeHandlerRegistry()
                                    .register(sourceEnumClass,
                                            new SourceEnumTypeHandler<>(sourceEnumClass,
                                                    sourceEnumGenericConverter())));
        }else if(mybatis == EnumValueStrategy.ORDINAL){
            configuration.setDefaultEnumTypeHandler(EnumOrdinalTypeHandler.class);
        }

    }

    @NoArgsConstructor
    @ConditionalOnClass(MybatisPlusAutoConfiguration.class)
    @Configuration
    private class MybatisPlusConfiguration {
        @Bean
        public com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer configurationCustomizer() {
            return EnumConverterAutoConfiguration.this::mybatis;
        }
    }

    @NoArgsConstructor
    @ConditionalOnClass(MybatisAutoConfiguration.class)
    @Configuration
    private class MybatisConfiguration{
        @Bean
        public org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer configurationCustomizer(){
            return EnumConverterAutoConfiguration.this::mybatis;
        }
    }


}
